from flask import request, jsonify, Blueprint
from flask.views import MethodView

from app.mod_base.args_parser import _parse_args


class APIView(MethodView):
    def __init__(self, model, args_schema, marsh_schema, **kwargs):
        self.model = model
        self.args_schema = args_schema
        self.marsh_schema = marsh_schema
        self.callback = kwargs.get('callback')
        self.validator = kwargs.get('validator')

    def get(self, id_):
        data = request.args
        if id_ is None:
            args, errs = _parse_args(data, self.args_schema.GET)
            if errs:
                return jsonify({'message': errs}), 400
            sort_field = args.get('sort_field', 'id')
            filters = args.get('filters', {})
            objects = self.model.objects(**filters).order_by(sort_field)
            x_total_count = objects.count()
            res = self.marsh_schema(many=True).dump(objects)
            ret = jsonify(res)
            ret.headers['X-Total-Count'] = str(x_total_count)
            return ret
        else:
            obj = self.model.objects(id=id_).first()
            res = self.marsh_schema().dump(obj)
            return jsonify(res)

    def put(self, id_):
        try:
            data = request.get_json(force=True)
            data, errs = _parse_args(data, self.args_schema.PUT)
            if errs:
                return jsonify({'message': errs}), 400
            if callable(self.validator):
                self.validator(method='PUT', model=self.model, data=data, id_=id_)
            self.model.objects(id=id_).update_one(**data)
            if callable(self.callback):
                self.callback(method='PUT', model=self.model, data=data, id_=id_)
            return jsonify({'message': 'success', 'id': id_})
        except Exception as e:
            return jsonify({'message': str(e)}), 400

    def delete(self, id_):
        if callable(self.callback):
            data = self.model.objects(id=id_).first()
            if not data:
                return jsonify({'message': 'data not exist'}), 400
            data.delete()
            self.callback(method='DELETE', model=self.model, data=data, id_=id_)
        else:
            self.model.objects(id=id_).delete()
        return jsonify({'message': 'delete success'})

    def post(self):
        try:
            data = request.get_json(force=True)
            data, errs = _parse_args(data, self.args_schema.POST)
            if errs:
                return jsonify({'message': errs}), 400
            if callable(self.validator):
                self.validator(method='POST', model=self.model, data=data, id_=None)
            obj = self.model(**data)
            obj.save()
            if callable(self.callback):
                self.callback(method='POST', model=self.model, data=data, id_=obj.id)
            return jsonify({'message': 'success', 'id': str(obj.id)})
        except Exception as e:
            return jsonify({'message': str(e)}), 400


EMPTY_METHODS = frozenset(('GET',))
NO_INST_METHODS = frozenset(('POST',))
INST_METHODS = frozenset(('GET', 'DELETE', 'PUT'))


def create_blueprint(model, api_name, args_schema, marsh_schema, bp_name=None,
                     methods=EMPTY_METHODS, url_prefix=None, **kwargs):
    bp_name = bp_name or f'bp_{api_name}'
    bp = Blueprint(bp_name, __name__, url_prefix=url_prefix)

    methods = frozenset((m.upper() for m in methods))
    empty_methods = methods & EMPTY_METHODS
    no_inst_methods = methods & NO_INST_METHODS
    inst_methods = methods & INST_METHODS

    end_point = f'/{api_name}'
    api_view = APIView.as_view(bp_name, model=model, args_schema=args_schema, marsh_schema=marsh_schema, **kwargs)
    bp.add_url_rule(end_point, defaults={'id_': None}, methods=empty_methods, view_func=api_view)
    bp.add_url_rule(end_point, methods=no_inst_methods, view_func=api_view)
    bp.add_url_rule(f'{end_point}/<id_>', methods=inst_methods, view_func=api_view)
    return bp
